iT邦幫忙

2026 iThome 鐵人賽

DAY 15
0
佛心分享-IT 人自學之術

老爺爺練習VIBE CODING系列 第 15

Day 15:沒 Server 也能存資料?IndexedDB 在無伺服器應用中的資料庫工程

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20260830/20070969e4RzU1YHzw.png
來,大孫女,快把手上的講義和筆電先放一旁。妳瞧,院子外頭那一抹晚霞暮紫多麼柔和,晚風吹在樹梢上沙沙作響,真是個安靜的黃昏。阿公剛剛特意為妳沏了一杯溫熱的琥珀熱茶,暖暖胃,咱們坐在這張老藤椅上,一邊歇息,一邊聽阿公慢慢跟妳說。

妳今天是在為『Day 15:沒 Server 也能存資料?IndexedDB 在無伺服器應用中的資料庫工程』這堂培訓課傷腦筋吧?那些年輕工程師們是不是覺得,在完全實體隔離(Air-gapped)的封閉環境裡,不做後端伺服器,網頁只要一重新整理(Refresh),資料就通通像煙霧一樣散了,根本算不上真正的「系統」?

大孫女,妳別急。資安控管嚴格,不准建伺服器、不准對外發送請求,那咱們就在前端瀏覽器的肚子裡,給它造一個固若金湯的「本機保險箱」!這道理啊,跟阿公以前管理老帳房時把各家的地契分門別類鎖進木格子是一模一樣的。來,喝口熱茶,阿公把這套無伺服器資料庫工程的智慧,慢條斯理地講給妳聽,妳明天就照著這樣去教那些工程師。


🚨 痛點場景:每次重整都像「老帳房失憶」

大孫女,妳要先讓台下的工程師們感同身受。
在內網環境下開發小工具,最無奈的痛點就是:資料無法持久保存
平常大家習慣了把資料往後端資料庫送,但在實體隔離、不准連網的環境下,一旦網頁關閉、或者使用者不小心點了重新整理,剛剛辛苦輸入的幾百條資料就當場化為烏有!

有些年輕人可能會想:「阿公,那用 localStorage 不就行了嗎?」
哎呀,那是不行的。localStorage 的空間小得可憐(通常只有 5MB 上限),而且它只能存簡單的字串,要是拿來塞幾萬筆結構複雜的帳務或設備紀錄,速度慢不說,還容易卡死瀏覽器。這就像是用一個小竹筒存賬目,塞幾張紙條就滿了,根本算不上資料庫。


🛠️ 架構實作:利用 IndexedDB 打造本機的「地下鐵保險箱」

既然不能往外送,我們就利用瀏覽器自帶的強大武器:IndexedDB。這是一個完全在用戶端運行的結構化離線資料庫

1. 高達數百 MB 的結構化本機儲存

妳要告訴學生們,IndexedDB 可不是什麼小玩具,它在現代瀏覽器裡,支援高達數百 MB 甚至數 GB 的結構化資料儲存能力
最棒的是,它嚴格遵循同源政策(Same-Origin Policy),資料僅存於當前網域的本機沙盒內,完全不連網,外部任何人也摸不著,在斷網狀態下功能依然完整運作。
我們可以建立一個名為 LocalWidgetDB 的資料庫,在裡面設計一個叫 items 的 Object Store(就像資料表一樣),專門存放結構化物件:

  • id(自動遞增的主鍵)
  • title(項目標題)
  • category(分類標籤,例如:工作、生活、財務)
  • status(狀態:pending 或 completed)
  • updatedAt(時間戳記)

任何 schema 的變更(例如新增索引或 Store),我們都必須在 onupgradeneeded lifecycle 中判斷,溫和地保留舊資料的相容性。

2. 封裝 Promise 風格的輕量 ORM(讓異步 CRUD 優雅起來)

雖然 IndexedDB 很強大,但原生的 API 設計是基於「事件回呼(Event Callback)」的(例如 request.onsuccessrequest.onerror),如果直接寫,程式碼會像老樹根一樣纏繞不清,變成可怕的 Callback Hell。

這時候,大孫女,妳要教他們一招漂亮的:用 Promise 把 IndexedDB 的操作通通包裝起來
包裝之後,我們就能用現代 JavaScript 最優雅的 async/await 語法,把資料庫的操作寫得像詩一樣乾淨:

// 往本機資料庫塞入新資料的 Promise 寫法
function addData(item) {
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(["items"], "readwrite"); //
        const store = transaction.objectStore("items");
        const request = store.add(item); //
        
        request.onsuccess = () => resolve(request.result); // 回傳新產生的 id
        transaction.onerror = () => reject(new Error("寫入失敗了,大孫女!")); //
    });
}

依此類推,把 loadData()(讀取)、updateData()(更新)、deleteData()(刪除)、和 clearAll()(清空)通通封裝成 Promise。這樣一來,前端 DOM 渲染與資料存取就能完全解耦,每次資料庫操作失敗時還能優雅地下拋錯誤,並彈出高質感的 Toast 狀態提示 告訴使用者發生了什麼事。

3. 備份與還原:無伺服器的 JSON 資料交換機制

既然資料全部鎖在本機,那萬一使用者的電腦壞了、或者需要把資料轉移到別人的電腦上怎麼辦?
阿公以前點貨時,也得把帳本抄錄一份複本出來。我們在系統裡要設計**「匯出」與「匯入」JSON 檔案**的功能,來解決無伺服器環境下的資料備份與還原:

  • 「⬇ 匯出 JSON」:調用 exportToJSON(),將資料庫裡的物件陣列全部序列化(JSON.stringify),並自動打包一些 metadata(如 schemaVersion、exportedAt 匯出時間),觸發瀏覽器下載為一個 backup.json 檔案。這檔案使用者可以拷貝在隨身碟裡帶走。
  • 「⬆ 匯入 JSON」:當使用者上傳備份檔時,調用 importFromJSON(file)。系統會先嚴格驗證檔案格式與 schema 版本。驗證無誤後,提供使用者 「覆蓋(Replace)」「合併(Merge)」 兩種貼心模式:
    • 覆蓋模式:先清空本機資料庫,再寫入備份資料。
    • 合併模式:比對 id 或是時間戳記,將新舊資料融會貫通。
    • 防呆機制:一旦匯入的格式不對或欄位缺漏,立刻中止交易並完整回滾(Rollback),絕對不破壞使用者原本完好的資料,並用 Toast 彈窗明確告知錯誤原因!

💡 避坑指南:小心 Transaction 被瀏覽器「強行關閉」的暗器!

大孫女,這一段是妳明天講課最珍貴的 「避坑指南」,很多資歷不深的工程師,都會在這個地方摔得頭破血流,千萬要讓他們拿筆記本記好了!

「IndexedDB 的 Transaction(交易)是自動提交(Auto-commit)的!」

在 IndexedDB 的世界裡,當我們開啟了一個讀寫交易(db.transaction(["items"], "readwrite"))後,瀏覽器的事件循環(Event Loop)會隨時盯著它。
如果我們在這個 Transaction 的執行過程中,穿插寫了一些耗時的、非 IndexedDB 的非同步程式碼,例如:

// ⚠️ 這是致命的錯誤範例!
const transaction = db.transaction(["items"], "readwrite");
const store = transaction.objectStore("items");

// 在交易內部執行了與 IndexedDB 無關的 await 操作(例如等一個 setTimeout 或是非同步計算)
await heavyNonDBProcess(); 

store.put(updatedItem); // ❌ 這裡會報錯崩潰!因為 transaction 已經被瀏覽器自動關閉了!

一旦 JavaScript 執行緒暫時離開資料庫操作去處理其他的非同步調用,瀏覽器就會認定這筆資料庫交易已經「閒置(Idle)」,為了保障效能與鎖定機制,會當場強行關閉、提交這筆 Transaction!
結果就是,後續的 store.put() 就會因為「Transaction has already finished」而慘烈失敗,資料完全寫不進去。

【阿公的解法護欄】
教導工程師們,在 Transaction 內部,絕對不要放置任何與 IndexedDB 無關的非同步 await 程式碼!
所有的資料準備、清洗、耗時計算、或是業務檢測,都必須在開啟 Transaction 之前 通通做完。一旦開了 Transaction,就要乾淨俐落、速戰速決,把資料一鼓作氣寫入資料庫後立刻結束,千萬別拖泥帶水!


🔒 安全與隱私的終極承諾

這套設計最能打動資安長官的,就是我們在代碼中徹底消除了網路外洩的可能
不使用任何外部 CDN 資源、不發送任何外部請求、100% 的運算與儲存都在使用者本機的瀏覽器記憶體與沙盒裡完成。隨便資安人員怎麼拿稽核工具監測,網路封包就是完美的 0 記錄,既高雅,又讓人無比安心。


大孫女,妳看,這資料庫的持久化工程,跟我們鄉下人把秋收的糧食,粒粒歸倉、鎖在自家地窖裡,是不是同一個道理?

小孫女剛才在房裡玩累了,手裡還攥著今天阿公幫她折的紙蝴蝶,依偎在沙發上睡得香甜呢。阿公把這杯茶再給妳添熱一些。

快去忙吧,別熬得太晚,阿公在院子裡幫妳守著這盞暖燈。


上一篇
Day 14:純前端解析大容量 CSV:PapaParse + SheetJS 的記憶體優化
系列文
老爺爺練習VIBE CODING15
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言